package com.ls.springbootdemo.util;

import java.util.Random;

/**
 * 雪花算法, 分布式id
 * 雪花算法是JavaLong类型的一个长整型数字，生成一个63位的有序数字（二进制第一位是符号位）
 *
 * @author : 挥之以墨
 * @date: 2020/8/4 21:08
 */
public class SnowFlakeUtil {

    /**
     * 初始时间值 Thu Nov 04 2010 09:42:54 GMT+0800 (中国标准时间)
     */
    private final long twepoch = 1288834974657L;
    /**
     * 一般情况是长度5的机房Id和长度5的机器id，这里直接把两个合并成一个
     */
    private final long workIdBits = 10L;
    /**
     * 工作id的范围是 0到1023
     */
    private final long maxWorkId = ~(-1L << workIdBits);
    /**
     * 每毫秒可以产生的id数长度
     */
    private final long sequenceBits = 12L;
    /**
     * 每毫秒产生的id的最大值为4095
     */
    private final long maxSequence = ~(-1L << sequenceBits);
    /**
     * 工作id对应的偏移量
     */
    private final long workIdShift = sequenceBits;
    /**
     * 时间戳对应的偏移量，Long长度一共为64bit，工作机器一共10bit，id长度12bit，所以只需要向左移动22bit
     */
    private final long timestampShift = sequenceBits + workIdBits;
    /**
     * 初始化workId
     */
    private long workId;
    /**
     * 缓存每毫秒的id，重复时id+1
     */
    private long sequence;
    /**
     * 缓存上次生成雪花id的时间，判断是不是同一毫秒，同时也可以判断服务器时间是否回滚。
     */
    private long lastTimeStamp = -1L;
    /**
     * 随机数对象，主要是避免id递增过于单调，可以猜到下一个id是啥
     */
    private Random random = new Random();

    /**
     * 单例对象
     */
    private static volatile SnowFlakeUtil instance = null;

    /**
     * 私有化构造函数
     */
    private SnowFlakeUtil() {
    }

    /**
     * 私有构造函数，并创建对象
     *
     * @param workId 机房与机器id的组合值
     */
    private SnowFlakeUtil(long workId) {
        if (workId < 0 || workId > maxWorkId) {
            throw new RuntimeException("The workId is wrong while snowFlake was initializing");
        }
        this.workId = workId;
    }

    /**
     * 初始化工具类
     */
    public static SnowFlakeUtil getInstance(long workId) {
        if (instance == null) {
            synchronized (SnowFlakeUtil.class) {
                if (instance == null) {
                    instance = new SnowFlakeUtil(workId);
                }
            }
        }
        return instance;
    }

    /**
     * 获取ID
     */
    public synchronized long getId() {
        long currentTimeStamp = System.currentTimeMillis();
        // 判断时间是否回滚了
        if (currentTimeStamp < lastTimeStamp) {
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                    lastTimeStamp - currentTimeStamp));
        }
        // 如果是同一毫秒，则处理id递增1
        if (currentTimeStamp == lastTimeStamp) {
            sequence = (sequence + 1) & maxSequence;
            // sequence等于0，表示下一个序列号为4096，超出了最大位数
            if (sequence == 0) {
                // 此时等待进入下1毫秒
                currentTimeStamp = tilNextMillis(lastTimeStamp);
                // 避免id过于单调
                sequence = random.nextInt(100);
            }
        } else {
            // 避免id过于单调
            sequence = random.nextInt(100);
        }
        lastTimeStamp = currentTimeStamp;
        // 时间戳左移22，工作机器id左移12，序列号保持原位。结果为时间戳占用41位，工作机器id占用10位，序列号占用12位
        return ((currentTimeStamp - twepoch) << timestampShift) | (workId << workIdShift) | sequence;
    }

    /**
     * 获取下1毫秒
     *
     * @param lastTimeStamp 最后一次使用的时间戳
     * @return 向未来借用的时间戳
     */
    private long tilNextMillis(long lastTimeStamp) {
        long currentTimeMillis = System.currentTimeMillis();
        while (currentTimeMillis <= lastTimeStamp) {
            currentTimeMillis = System.currentTimeMillis();
        }
        return currentTimeMillis;
    }

}
